﻿#include "BTEServer.h"
#include <QDebug>
#include <QMessageBox>
#include <QMenu>
#include <QFile>
#include <QDataStream>
#include <QDir>
#include <QApplication>
#include <QShortcut>
#include <functional>
#include <QInputDialog>
#include <QSettings>
#include "xlsxwriter.h"
#include "DataTrans.h"
#include "StepSettingDialog.h"
#include "StepDataDialog.h"
#include "StepEditDialog.h"
#include "StepInfoDialog.h"
#include "SplitDialog.h"
#include "StepJumpDialog.h"
#include "LogManager.h"
#include "HelpDialog.h"
#include "DeviceMacDialog.h"
#include <QClipboard>

#define HIDE_TEST_PAGE 1

DeviceInfo::DeviceInfo()
{
    netId = -1;
	DeviceStatus.resize(MAX_DEVICE_ITEM);
	DeviceStatus.fill(Status_Complete);

	onLineStatus.resize(MAX_DEVICE_ITEM);
	onLineStatus.fill(false);

	stepSendWait = false;
	stepSendTime = QDateTime::currentDateTime();
}

//
BTEServer::BTEServer(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);    
    m_actLang = new QActionGroup(this);
    m_actLang->addAction(ui.actionChn);
    m_actLang->addAction(ui.actionEng);
    
    QSettings set(qApp->applicationDirPath() + "/option.ini", QSettings::IniFormat);
    int lang = set.value("lang", 0).toInt();
    if (lang == 1)
    {        
        ui.actionEng->setChecked(true);        
    }

    g_project = &m_project;
    m_project.init();
    loadProject();
    for (int i = 0; i < 16; i++)
        m_guid[i] = 0x40 + i;

    initTable();

    m_respMap.insert(BTS_CMD_SET_RTC_RESP, BTS_CMD_SET_RTC);
    m_respMap.insert(BGS66_CMD_SET_RT_INTERVAL_RESP, BGS66_CMD_SET_RT_INTERVAL);
    m_respMap.insert(BTS_CMD_SET_STEP_EX_RESP76, BTS_CMD_SET_STEP76_Ex);
    m_respMap.insert(BTS_CMD_START_RESP70, BTS_CMD_START70);
    m_respMap.insert(BTS_CMD_RESUME_RESP, BTS_CMD_RESUME);
    m_respMap.insert(BTS_CMD_STOP_RESP, BTS_CMD_STOP);
    m_respMap.insert(BTS_CMD_STEP_JUMP_RESP, BTS_CMD_STEP_JUMP);
    m_respMap.insert(BTS_CMD_SEND_PARALLEL_RESP, BTS_CMD_SEND_PARALLEL);
    m_respMap.insert(BTS_CMD_RESUME_CONFIRM_RESP, BTS_CMD_RESUME_CONFIRM);

    m_stepDataDialog = new StepDataDialog(this);

    //创建连接
    connect(&m_server, SIGNAL(newConnect(int)), this, SLOT(onNewConnect(int)), Qt::QueuedConnection);
    connect(&m_server, SIGNAL(disConnect(int)), this, SLOT(onDisConnect(int)), Qt::QueuedConnection);
    connect(&m_server, SIGNAL(newDataRecv(int, NetPackPtr)), this, SLOT(onDataRecv(int, NetPackPtr)), Qt::QueuedConnection);

    connect(ui.splitSettingWidget, SIGNAL(sigPrepareData()), this, SLOT(onSplitPrepareData()));
    connect(ui.splitSettingWidget, SIGNAL(sigSplitFinish()), this, SLOT(onSplitFinish()));
    connect(&m_heartTimer, SIGNAL(timeout()), this, SLOT(onHeartTime()));
    connect(&m_autoTimer, SIGNAL(timeout()), this, SLOT(onAutoTime()));
    connect(&m_stepTimer, SIGNAL(timeout()), this, SLOT(onStepTime()));

    connect(m_stepDataDialog, SIGNAL(sigChannelChanged(int)), this, SLOT(onStepDataChannelChanged(int)));

    ui.listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(ui.listWidget, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onListItemMenu(const QPoint&)));

    //设置item
    QGridLayout *layout = new QGridLayout();
    for (int i = 0; i < MAX_SUB_DEVICE * MAX_CHANNEL_NUM; i++)
    {
        int row = i / 8;
        int col = i % 8;

        BTEItemWidget *item = new BTEItemWidget();
        layout->addWidget(item, row, col);
        item->setVisible(false);
        item->setChannelText(QString::asprintf("%d - %d", row + 1, col + 1));
        item->setItemIndex(i);

        connect(item, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onBTEItemMenu(const QPoint&)));
        connect(item, SIGNAL(toggled(bool)), this, SLOT(onBTEItemChecked(bool)));

        m_itemList.push_back(item);
    }
    layout->setColumnStretch(8, 1);
    layout->setRowStretch(8, 1);
    layout->setMargin(3);
    layout->setSpacing(3);
    ui.itemWidgets->setLayout(layout);

    initShortCuts();
    //	
    ui.tabWidget->setCurrentIndex(0);
    ui.tabWidgetSplit->setCurrentIndex(0);

#if HIDE_TEST_PAGE
    ui.btnShowTest->hide();
#else
    LogManager::instance()->setLogLevel(LOG_DEBUG);
#endif

    on_tabWidget_currentChanged(0);
    m_selectWidget = new QWidget(this);
    m_selectWidget->setStyleSheet("border: 2px solid rgb(255, 255, 127);");
    m_selectWidget->hide();

	//启动服务
	m_dispIndex = 0;
    m_mouseDown = false;
	
	m_server.start();
	m_server.init(8001);

	m_heartTimer.start(1000);
	m_autoTimer.start(60 * 1000);	
	m_stepTimer.start(100);
	initDeviceList();	    

	LogInfo("start program");
}

BTEServer::~BTEServer()
{	
	LogInfo("exit program");

	saveProject();
}

void BTEServer::loadProject()
{
	m_project.load();
}

void BTEServer::saveProject()
{	
	m_project.save();
}

void BTEServer::CrashExit()
{
	//保存
	m_project.save();

	//关闭服务
	m_server.deinit();	
}

void BTEServer::initTable()
{
	//tableQuery
	QStringList strListHeader;
	strListHeader << tr("记录号") << tr("通道号") << tr("分选") << tr("启动时间") << tr("结束时间") << tr("记录条数") << tr("电池编号")
		<< tr("批号/电池型号") << tr("创建者") << tr("备注");
	UiHelper::SetTable(ui.tableQuery, strListHeader);

	ui.tableQuery->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(ui.tableQuery, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onTableQueryMenu(const QPoint&)));

	//tableList
	QStringList strListList;
	strListList << tr("通道号") << tr("电池编号") << tr("工步") << tr("循环") << tr("工作状态") << tr("电压") << tr("电流") 
		<< tr("时间") << tr("实充容量") << tr("实放容量") << tr("实充能量") << tr("实放能量") << tr("终止充电容量") << tr("终止容量");
	UiHelper::SetTable(ui.tableList, strListList);

	ui.tableList->setRowCount(MAX_DEVICE_ITEM);
	for (int i = 0; i < MAX_DEVICE_ITEM; i++)
	{
		auto itemNo = new QTableWidgetItem();
		itemNo->setText(QString::asprintf("%02d-%02d", i / 8 + 1, i % 8 + 1));
		ui.tableList->setItem(i, 0, itemNo);
		for (int j = 1; j < strListList.size(); j++)
			ui.tableList->setItem(i, j, new QTableWidgetItem());
	}

	//tableSplitLevel
	QStringList strListSplitLevel;
	strListSplitLevel << tr("级别") << tr("电池个数") << tr("点灯");
	UiHelper::SetTable(ui.tableSplitLevel, strListSplitLevel);

	//tableSplitList
	QStringList strListSplitList;
	strListSplitList << tr("序号") << tr("单元-通道") << tr("放电容量(Ah)") << tr("放电时间（H:M:S:MS）")
		<< tr("中值电压（V）") << tr("级别") << tr("电池编号");
	UiHelper::SetTable(ui.tableSplitList, strListSplitList);
}

void BTEServer::closeEvent(QCloseEvent *event)
{
	bool can_close = true;

	auto it = m_devices.begin();
	while (it != m_devices.end())
	{		
		for (int i = 0; i < it->DeviceStatus.size(); i++)
		{
			if (it->DeviceStatus[i] != DeviceInfo::Status_Complete
				&& it->DeviceStatus[i] != DeviceInfo::Status_Stop)
			{
				can_close = false;
				break;
			}
		}
		it++;
	}

	if (!can_close)
	{
		event->ignore();
		QMessageBox::information(this, "", tr("还有工步在运行，无法退出"));
		return;
	}

	m_server.deinit();
	QMainWindow::closeEvent(event);
}

void BTEServer::paintEvent(QPaintEvent *event)
{
    QMainWindow::paintEvent(event);
}

void BTEServer::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton && ui.stackedWidget->currentIndex() == 0 
        && ui.stackedWidget->geometry().contains(event->pos()))
    {
        m_mouseDown = true;
        m_downPoint = event->pos();
    }
}

void BTEServer::mouseMoveEvent(QMouseEvent *event)
{
    m_curPoint = event->pos();
    if (m_mouseDown && (m_curPoint - m_downPoint).manhattanLength() > 40)
    {
        m_selectWidget->setGeometry(QRect(m_downPoint, m_curPoint).normalized());
        m_selectWidget->show();
        m_selectWidget->raise();
        this->update();
    }
}

void BTEServer::mouseReleaseEvent(QMouseEvent *event)
{
    if (m_mouseDown && m_selectWidget->isVisible())
    {
        QRect sel = m_selectWidget->geometry();

        bool first = true;        
        int w = m_itemList[0]->width();
        int h = m_itemList[0]->height();
        for (int i = 0; i < m_itemList.size(); i++)
        {
            if (!m_itemList[i]->isVisible())
                continue;

            QPoint p1 = m_itemList[i]->mapTo(this,QPoint(0,0));
            QPoint p2 = m_itemList[i]->mapTo(this,QPoint(w,h));

            QRect rc(p1, p2);
            if (sel.intersects(rc))
            {
                if (first && QApplication::keyboardModifiers() != Qt::ControlModifier)
                {
                    for (int j = 0; j < m_itemList.size(); j++)
                        m_itemList[j]->setCheck(false);
                }

                m_itemList[i]->setCheck(true);
                first = false;
            }
        }
    }

    m_mouseDown = false;
    m_selectWidget->hide();
}

void BTEServer::initShortCuts()
{
	QShortcut *selectAll = new QShortcut(QKeySequence(tr("Ctrl+A")), this);	
	connect(selectAll, SIGNAL(activated()), this, SLOT(onBTEItemSelectAll()));
}

void BTEServer::dispPack(const NetPack &pack, QTextEdit *textEdit)
{
	if (!ui.boxRecordPack->isChecked())
		return;

	if (ui.boxShowRT->isChecked() && pack.info.CmdID == BTS_CMD_SEND_RTDATA76)
		return;
	if (ui.boxShowDF->isChecked() && (pack.info.CmdID == BTS_CMD_SEND_DFDATA76
		|| pack.info.CmdID == BTS_CMD_SEND_DFDATA_RESP76))
		return;

	QByteArray pack_data = m_manager.packData(pack);	
	QString disp;
	for (int i = 0; i < pack_data.size(); i++)
	{
		disp += QString::asprintf("%02X ", (uchar)pack_data[i]);
		if ((i + 1) % 16 == 0 && i != pack_data.size() - 1)
			disp += "\n";
	}
	disp += "\n";

	QString time = QDateTime::currentDateTime().toString("[HH:mm:ss:zzz] ");
	QString title = time + QString::asprintf("idx = %d,cmd = %#04X", m_dispIndex, pack.info.CmdID);
	if (pack.info.IsRecvPack())
		title += QString::asprintf(",ret = %#02X", pack.info.Result);	
	
	textEdit->append(title + "\n" + disp);
	ui.textLog->append(title);

	m_dispIndex++;
}

void BTEServer::send(int netId, ushort cmd, const QByteArray &data, uchar SubDevID)
{	
	Q_ASSERT(cmd && m_devices.contains(netId));

	auto &device = m_devices[netId];
	if (SubDevID != INVAILD_SUB_ID)
		SubDevID += 1;

	NetPackVariant pack;
	pack.info.DeviceID = device.DeviceID;
	pack.info.SubDevID = SubDevID;
	pack.info.Length = BTS_HEAD_LENGTH + BTS_SEND_HEAD_LENGTH + data.length() + BTS_TAIL_LENGTH;
	pack.info.CmdID = cmd;
	pack.data = data;

	LogInfo("device %s send %#04X", qUtf8Printable(device.DeviceString), pack.info.CmdID);
	dispPack(pack,ui.textSend);
	m_server.sendPack(netId, pack);
}

void BTEServer::send(int netId, StepRequestPtr pack)
{
	QByteArray body;
	pack->pack(body);

	send(netId, pack->type(), body, pack->SubDeviceId);
}

void BTEServer::sendParallel(int netId, QVector<int> itemIdx)
{
	Q_ASSERT(m_devices.contains(netId));

	DeviceInfo &device = m_devices[netId];	
	for(int i = 0; i < itemIdx.size(); i++)
	{
		int pal_idx = -1;
		for (int pal = 0; pal < device.data->parallelList.size(); pal++)
		{
			if (device.data->parallelList[pal].channels.contains(itemIdx[i]))
			{
				pal_idx = pal;
				break;
			}
		}
		if (pal_idx < 0)
			continue;

		ParallelInfo p = device.data->parallelList[pal_idx];

		auto pack = new stru_BTS_CMD_SEND_PARALLEL();
		pack->SubDeviceId = p.channels[0] / 8;
		for (int i = 0; i < p.channels.size(); i++)
			pack->channelInfo.push_back(p.channels[i] % 8);
		
		device.stepPackList.push_back(StepRequestPtr(pack));
	}

	sendStepPack(netId);
}

void BTEServer::sendStepPack(int netId)
{
    if (!m_devices.contains(netId))
        return;

	//发送
	DeviceInfo &device = m_devices[netId];
	if (!device.stepSendWait && device.stepPackList.size() > 0) //device.stepSendTime.msecsTo(QDateTime::currentDateTime()) >= 1000)
	{		
		device.stepSendTime = QDateTime::currentDateTime();
		device.stepSendWait = true;
		send(netId, device.stepPackList.front());
	}
}

void BTEServer::response(int netId, ushort cmd, uchar result, uchar SubDevID, QByteArray data)
{
	Q_ASSERT(m_devices.contains(netId));
	auto &device = m_devices[netId];

	NetPackVariant pack;
	pack.info.DeviceID = device.DeviceID;
	pack.info.SubDevID = SubDevID;
	pack.info.Length = BTS_HEAD_LENGTH + BTS_RECV_HEAD_LENGTH + data.length() + BTS_TAIL_LENGTH;
	pack.info.CmdID  = cmd;
	pack.info.Result = result;
	pack.data = data;	

	LogInfo("device %s response %#04X", qUtf8Printable(device.DeviceString), pack.info.CmdID);
	dispPack(pack, ui.textSend);
	m_server.sendPack(netId, pack);
}

void BTEServer::response(int netId, const stru_BTS_CMD_SEND_DFDATA_RESP76 &data)
{
	QByteArray body;
	data.pack(body);

	response(netId, BTS_CMD_SEND_DFDATA_RESP76, 0, 0, body);
}

void BTEServer::onNewConnect(int netId)
{
	NetInfo netinfo = m_server.netInfo(netId);
	LogInfo("connect ip = %s:%d", qUtf8Printable(netinfo.ip), netinfo.port);

	DeviceInfo info;
	info.data = NULL;
    info.netId = netId;
	m_devices.insert(netId, info);
}

void BTEServer::onDisConnect(int netId)
{	
	disconnect(netId);		
}

void BTEServer::disconnect(int netId)
{
	if (!m_devices.contains(netId))
		return;

	int count = ui.listWidget->count();
	for (int i = 0; i < count; i++)
	{
		QListWidgetItem *item = ui.listWidget->item(i);
		if (item->data(Qt::UserRole).toInt() == netId)
		{
			QString deviceId = item->data(Qt::UserRole + 1).toString();
			item->setData(Qt::UserRole, -1);
			item->setText(deviceId + tr("(离线)"));
			break;
		}
	}

	LogInfo("disconnect ip = %s", qUtf8Printable(m_devices[netId].DeviceString));
	m_devices.remove(netId);	
}

int BTEServer::deviceToNet(QString deviceId)
{
	auto it = m_devices.begin();
	while (it != m_devices.end())
	{		
		if (it->DeviceString == deviceId)
			return it.key();
		
		it++;
	}

	return -1;
}

int BTEServer::curNetId()
{
	int idx = ui.listWidget->currentRow();
	return idx < 0 ? -1 : ui.listWidget->item(idx)->data(Qt::UserRole).toInt();
}

QString BTEServer::curDeviceString()
{
	int idx = ui.listWidget->currentRow();
	return idx < 0 ? QString() : ui.listWidget->item(idx)->data(Qt::UserRole + 1).toString();
}

void BTEServer::on_actionExit_triggered()
{
	close();
}

void BTEServer::on_actionAbout_triggered()
{
	HelpDialog dlg(this);
	dlg.exec();
}

void BTEServer::switchLang(int lang)
{
    switchAppLang(lang);    
    ui.retranslateUi(this);
}

void BTEServer::on_actionChn_triggered()
{
    QSettings set(qApp->applicationDirPath() + "/option.ini", QSettings::IniFormat);
    set.setValue("lang", 0);
    switchLang(0);
}

void BTEServer::on_actionEng_triggered()
{
    QSettings set(qApp->applicationDirPath() + "/option.ini", QSettings::IniFormat);
    set.setValue("lang", 1);
    switchLang(1);
}

void BTEServer::onDataRecv(int netId, NetPackPtr pack)
{
	NetPackVariant *ptr = (NetPackVariant*)pack.data();
	dispPack(*ptr, ui.textRecv);

	if (!m_devices.contains(netId))
	{
		LogError("invaild device");
		return;
	}

	int cmd = ptr->info.CmdID;	
	DeviceInfo &device = m_devices[netId];
	LogInfo("device %s recv pack: %#04X", qUtf8Printable(device.DeviceString),cmd);

	if (cmd == BTS_CMD_CONNECT)
	{						
		device.DeviceID = ptr->info.DeviceID;
		response(netId, BTS_CMD_CONNECT_RESP, 0);
		return;
	}		
	else if (cmd == BTS_CMD_SEND_INFO)
	{
		stru_BTS_CMD_SEND_INFO info;
		if (info.unpack(ptr->data))
		{
			NetInfo netInfo = m_server.netInfo(netId);
			QString DeviceString = QString::asprintf("%s-%dV%dA", qPrintable(netInfo.ip), info.VoltageMax, info.ChargeMax / 1000);

			auto it = m_devices.begin();
			while (it != m_devices.end())
			{
				//重复,删除已有连接
				if (it->DeviceString == DeviceString && it.key() != netId)
				{
					int old_id = it.key();
					m_server.closeConnect(old_id);
					disconnect(old_id);
					break;
				}
				it++;
			}

			device.DeviceID = ptr->info.DeviceID;
			device.DeviceString = DeviceString;

			if (!device.data)
			{
				device.data = m_project.deviceData(DeviceString);
				initDeviceList();				
			}

			device.data->deviceInfo = info;
			if (curNetId() == netId)
				initDevice();

			//回复
			response(netId, BTS_CMD_SEND_INFO_RESP, 0);

			//set rtc
			stru_BTS_CMD_SET_RTC *rtc = new stru_BTS_CMD_SET_RTC();
			rtc->time = QDateTime::currentDateTime();
			device.stepPackList << StepRequestPtr(rtc);

			//set interval
			stru_BGS66_CMD_SET_RT_INTERVAL *RTInterval = new stru_BGS66_CMD_SET_RT_INTERVAL();
			RTInterval->RTInterval = 1;
			device.stepPackList << StepRequestPtr(RTInterval);

			QTimer::singleShot(200, [this, netId] { this->sendStepPack(netId); });
		}
		return;
	}	
	if (!device.data)
	{
		LogError("error pack seq type = %#x", cmd);
		return;
	}
	
	if(cmd == BTS_CMD_HEART_BEAT)	
	{		
		device.heartTime = QDateTime::currentDateTime();
	}		
	else if (cmd == BTS_CMD_RESUME_QUERY)
	{
		response(netId, BTS_CMD_RESUME_QUERY_RESP, 0);
        showWaitResume(device);
	}	
	else if (m_respMap.contains(cmd))
	{
		if (cmd == BTS_CMD_RESUME_CONFIRM_RESP)
		{
		}
		else if (cmd == BTS_CMD_SET_RTC_RESP)
		{			
		}
		else if (cmd == BGS66_CMD_SET_RT_INTERVAL_RESP)
		{
		}
		else if (cmd == BTS_CMD_SET_STEP_EX_RESP76)
		{		
		}		
		else if (cmd == BTS_CMD_START_RESP70)
		{
			stru_BTS_CMD_START_RESP70 start_res;
			start_res.unpack(ptr->data);			

			int sub_idx = ptr->info.SubDevID - 1;		
			for (int i = 0; i < start_res.ChannelCount; i++)
			{
				int item_idx = sub_idx * MAX_CHANNEL_NUM + (start_res.channelInfo[i].ChannelIDByNum - 1);
				if (item_idx >= 0 && item_idx < m_itemList.size())
				{
					if (device.DeviceStatus[item_idx] == DeviceInfo::Status_WaitStart)
						setItemStatus(netId, item_idx, DeviceInfo::Status_Run);
				}
				else
				{
					LogError("BTS_CMD_START_RESP70 sub_id = %d,chn = %d", ptr->info.SubDevID, start_res.channelInfo[i].ChannelIDByNum);
					break;
				}
			}
		}		
		else if (cmd == BTS_CMD_STOP_RESP)
		{
			stru_BTS_CMD_STOP_RESP stop_res;
			stop_res.unpack(ptr->data);

			int sub_idx = ptr->info.SubDevID - 1;
			QVector<int> stopList;
			for (int i = 0; i < 8; i++)
			{
				int item_idx = sub_idx * MAX_CHANNEL_NUM + i;
				if (item_idx >= 0 && item_idx < m_itemList.size())
				{
					int chn = (1 << i);
					if (chn & stop_res.ChannelID[0])
						stopList << item_idx;															
				}
				else
				{
					stopList.clear();
					LogError("BTS_CMD_STOP_RESP sub_id = %d", ptr->info.SubDevID);
					break;
				}								
			}
			if (stopList.size() > 0)
				recordsStop(netId, stopList);
		}
		else if (cmd == BTS_CMD_RESUME_RESP)
		{
			stru_BTS_CMD_RESUME_RESP resume_res;
			resume_res.unpack(ptr->data);

			int sub_idx = ptr->info.SubDevID - 1;
			for (int i = 0; i < 8; i++)
			{
				int item_idx = sub_idx * MAX_CHANNEL_NUM + i;
				if (item_idx >= 0 && item_idx < m_itemList.size())
				{
					if (device.DeviceStatus[item_idx] == DeviceInfo::Status_WaitStart)
						setItemStatus(netId, item_idx, DeviceInfo::Status_Run);
				}
				else
				{
					LogError("BTS_CMD_RESUME_RESP sub_id = %d", ptr->info.SubDevID);
					break;
				}
			}
		}
		else if (cmd == BTS_CMD_STEP_JUMP_RESP)
		{
			stru_BTS_CMD_STEP_JUMP_RESP jump_res;
			jump_res.unpack(ptr->data);

			int sub_idx = ptr->info.SubDevID - 1;
			for (int i = 0; i < 8; i++)
			{
				int item_idx = sub_idx * MAX_CHANNEL_NUM + i;
				if (item_idx >= 0 && item_idx < m_itemList.size())
				{
					if (device.DeviceStatus[item_idx] == DeviceInfo::Status_WaitJump)
						setItemStatus(netId, item_idx, DeviceInfo::Status_Run);
				}
				else
				{
					LogError("BTS_CMD_STEP_JUMP_RESP sub_id = %d", ptr->info.SubDevID);
					break;
				}
			}
		}
		else if (cmd == BTS_CMD_SEND_PARALLEL_RESP)
		{
		}		

		if (device.stepPackList.size() > 0 && 
			m_respMap[cmd] == device.stepPackList.front()->type())
		{
			device.stepSendWait = false;
			device.stepPackList.pop_front();
		}
		sendStepPack(netId);
	}
	else if(cmd == BTS_CMD_SEND_RTDATA76)
	{			
		stru_BTS_CMD_SEND_RTDATA76 data76;
		if (data76.unpack(ptr->data))
		{		
			int item_idx = (ptr->info.SubDevID - 1) * 8 + data76.ChannelIDByNum - 1;			
			if (item_idx >= 0 && item_idx < m_itemList.size())
			{
				device.onLineStatus[item_idx] = true;

				//上位机异常中断，恢复状态
				if (data76.StepType != STEP_TYPE_NULL && data76.workStatus() == stru_BTS_CMD_SEND_RTDATA76::WORK_RUN
					&& (device.DeviceStatus[item_idx] == DeviceInfo::Status_Complete || device.DeviceStatus[item_idx] == DeviceInfo::Status_Stop))
				{										
					m_project.resumeRecord(device.DeviceString, item_idx);
					setItemStatus(netId, item_idx, DeviceInfo::Status_Run);

					LogInfo("channel resume ip = %s,index = %d", qUtf8Printable(device.DeviceString), item_idx);
				}

				//操作出错
				if (data76.workStatus() == stru_BTS_CMD_SEND_RTDATA76::WORK_USER_STOP
					|| data76.workStatus() == stru_BTS_CMD_SEND_RTDATA76::WORK_ERROR_STOP)
				{
					setItemStatus(netId, item_idx, DeviceInfo::Status_Stop);
					if(data76.workStatus() == stru_BTS_CMD_SEND_RTDATA76::WORK_ERROR_STOP)
						LogInfo("channel error ip = %s,index = %d,code = %#0X", qUtf8Printable(device.DeviceString), item_idx,data76.PrtCode);
				}

				m_project.setRTData(device.DeviceString, item_idx,data76);
				if(netId == curNetId())
					setItemData(item_idx, data76);

				//结束
				if (data76.StepType == STEP_TYPE_STOP)
				{
					QVector<int> item_idxs;
					item_idxs << item_idx;
					recordsFinish(netId, item_idxs);
				}
			}
			else
			{
				LogError("BTS_CMD_SEND_RTDATA76 sub_id = %d,chn = %d", ptr->info.SubDevID, data76.ChannelIDByNum);
			}
		}			
	}	
	else if(cmd == BTS_CMD_SEND_DFDATA76)
	{
		stru_BTS_CMD_SEND_DFDATA76 df76;
		if (df76.unpack(ptr->data))
		{
			QString DeviceString = device.DeviceString;
			for (int i = 0; i < df76.DFDataNumber; i++)
			{
				auto &data = df76.dataList[i];
				int item_idx = (data.SubDevID - 1) * 8 + data.ChannelIDByNum - 1;
				if (item_idx >= 0 && item_idx < m_itemList.size())
				{
					if (data.DataFlag == 0x55)
					{
						if(m_project.record(DeviceString, item_idx, data) == STATUS_OK)
							m_stepDataDialog->addRealStep(DeviceString, item_idx,data);						
					}
					if (netId == curNetId())
					{	
                        if (PackDefine::StepChargeType(data.StepType) == STEP_CHARGE)
                            setItemLastChargeCap(item_idx, DataTrans::fromCapUnit(data.ChargeCap));
                        if(PackDefine::StepChargeType(data.StepType) == STEP_DISCHARGE)
						    setItemLastDisChargeCap(item_idx, DataTrans::fromCapUnit(data.DisChargeCap));
					}						
				}
				else
				{
					LogError("BTS_CMD_SEND_DFDATA76 sub_id = %d,chn = %d", data.SubDevID, data.ChannelIDByNum);
					break;
				}
			}

			stru_BTS_CMD_SEND_DFDATA_RESP76 df76_res;
			df76_res.Index = df76.DFDataPointer;
			response(netId, df76_res);
		}
	}
    else if (cmd == BTS_CMD_LED_ON_RESP)
    {

    }
	else
	{		
		LogError("unknow pack,type = %#x", cmd);
	}
}


//根据状态，创建一个中位机通信队列
template<class T>
QVector<StepRequestPtr> createPackList(DeviceInfo &deviceInfo, QVector<int> channels[], QVector<int> enableStatus, const T &base,std::function<void(int)> func = NULL)
{
	QVector<StepRequestPtr> list;
	//遍历列表
	for (int i = 0; i < MAX_SUB_DEVICE; i++)
	{		
		if (channels[i].size() == 0)
			continue;

		StepRequestPtr pack = StepRequestPtr(new T(base));
		pack->SubDeviceId = i;		

		int chn_num = 0;
		for (int chn = 0; chn < channels[i].size(); chn++)
		{
			int item_idx = channels[i][chn];
			int chn_idx = item_idx % MAX_CHANNEL_NUM;
			if (enableStatus.indexOf(deviceInfo.DeviceStatus[item_idx]) >= 0)
			{
				pack->setChannel(chn_idx);
				if(func)
					func(item_idx);

				chn_num++;
			}
		}
		
		if (chn_num > 0)
			list.push_back(pack);		
	}
	return list;
}

class MyValidator : public QValidator
{
public:
    MyValidator(QObject *parent,int type)
        :QValidator(parent)
    {
        QString ipReg = "\\.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})";
        if(type == 0)
            reg.setPattern("^192\\.168" + ipReg + ipReg + "$");
        else
            reg.setPattern("^192\\.168.1" + ipReg + "$");

        bool flag = reg.exactMatch("192.168.0.0");
    }

    virtual State validate(QString &input, int &pos) const override
    {
        if (!reg.exactMatch(input))
            return Invalid;
        else
            return Acceptable;
    }

    QRegExp reg;
};

void BTEServer::onListItemMenu(const QPoint &pt)
{
    int row = ui.listWidget->currentRow();
    if (row < 0)
        return;

    auto getIp = [this](QString ip,int ipType)->QString
    {
        QDialog dlg(this);
        QVBoxLayout *vlayout = new QVBoxLayout();
        QHBoxLayout *hlayout = new QHBoxLayout();

        QPushButton *btnOK = new QPushButton(tr("确定"));
        QPushButton *btnCancel = new QPushButton(tr("取消"));
        btnOK->setMinimumWidth(120);
        btnCancel->setMinimumWidth(120);
        hlayout->addStretch();
        hlayout->addWidget(btnOK);
        hlayout->addStretch();
        hlayout->addWidget(btnCancel);
        hlayout->addStretch();
        hlayout->setMargin(0);
        connect(btnOK, &QPushButton::clicked, &dlg, &QDialog::accept);
        connect(btnCancel, &QPushButton::clicked, &dlg, &QDialog::reject);

        QHBoxLayout *layout = new QHBoxLayout();
        layout->setMargin(0);
        layout->addWidget(new QLabel("IP:"));        

        QLineEdit *line = new QLineEdit();       

        MyValidator *validator = new MyValidator(line, ipType);
        line->setValidator(validator);      
        layout->addWidget(line);
        line->setText(ip);

        vlayout->addWidget(new QLabel(tr("请输入IP")));
        vlayout->addLayout(layout);
        vlayout->addLayout(hlayout);
        dlg.setLayout(vlayout);
        if (dlg.exec() != QDialog::Accepted)
            return QString();

        return line->text();
    };
    
    QListWidgetItem *item = ui.listWidget->item(row);
    QString deviceId = item->data(Qt::UserRole + 1).toString();   
    int netId = item->data(Qt::UserRole).toInt();
    if (netId < 0)
        return;
   
    QMenu menu;
    QAction *act1 = menu.addAction(tr("更改中位机IP"));
    /*
    QAction *act2 = menu.addAction(tr("写PC服务器IP"));
    QAction *act3 = menu.addAction(tr("写中位机号与MAC号"));
    */

    QAction *act = menu.exec(ui.listWidget->mapToGlobal(pt));
    if (act == act1)
    {                
        QString ip = getIp(deviceId.split("-")[0],0);
        if (!ip.isEmpty())
        {
            QStringList list = ip.split(".");
            if (list.size() == 4)
            {
                QByteArray data;
                data.push_back((char)list[2].toInt());
                data.push_back((char)list[3].toInt());
                send(netId, WRITE_DEVICE_IP, data);
            }
            else
            {
                QMessageBox::information(this, "", tr("IP 不合法，请检查"));
            }            
        }
    }/*
    else if (act == act2)
    {        
        QString ip = getIp(deviceId.split("-")[0],1);
        if (!ip.isEmpty())
        {
            QStringList list = ip.split(".");
            if (list.size() == 4)
            {
                QByteArray data;
                data.push_back((char)list[2].toInt());
                data.push_back((char)list[3].toInt());
                send(netId, WRITE_PC_IP, data);
            }
            else
            {
                QMessageBox::information(this, "", tr("IP 不合法，请检查"));
            }
        }
    }
    else if (act == act3)
    {
        DeviceMacDialog dlg(this);
        dlg.mac = 0;
        dlg.dev = 0;
        if (dlg.exec() != QDialog::Accepted)
            return;

        QByteArray data;
        data.push_back(dlg.dev);
        data.push_back(dlg.mac);        
        send(netId, WRITE_DEVICE_MAC, data);
    }
    */
}

bool BTEServer::checkDevice(int netId)
{
	if (!m_devices.contains(netId))
	{
		QMessageBox::information(this, "", tr("设备已离线"));
		return false;
	}
	return true;
}

void BTEServer::onBTEItemMenu(const QPoint &pt)
{
	QString DeviceString = curDeviceString();
	if (DeviceString.isEmpty())
		return;	

	int netId = curNetId();	

	BTEItemWidget *item = qobject_cast<BTEItemWidget*>(sender());			
	int first_item = item->itemIndex();	
	
	bool startEnable = false, stopEnable = false, resumeEnable = false, finishEnable = false;	
	bool setCountEnable = true;
	bool paraEnable = false;

	if (netId > 0)
	{
		//确认通道状态已经更新
		bool onLineCheck = true;
		DeviceInfo &device = m_devices[netId];
		for (int i = 0; i < device.onLineStatus.size(); i++)
		{
			if (!device.onLineStatus[i])
			{
				onLineCheck = false;
				break;
			}
		}

		if (onLineCheck)
		{
			startEnable = (device.DeviceStatus[first_item] == DeviceInfo::Status_Complete
				|| device.DeviceStatus[first_item] == DeviceInfo::Status_Stop);
			stopEnable = (device.DeviceStatus[first_item] == DeviceInfo::Status_Run);
			resumeEnable = (device.DeviceStatus[first_item] == DeviceInfo::Status_Stop);
			finishEnable = (device.DeviceStatus[first_item] == DeviceInfo::Status_Stop);			
			paraEnable = true;
		}
	}

	auto deviceData = m_project.deviceData(DeviceString);
	for (int i = 0; i < MAX_DEVICE_ITEM; i++)
	{		
		int stepType = deviceData->recordList[i]->info.rtData.StepType;
		if (stepType != STEP_TYPE_STOP && stepType != STEP_TYPE_NULL)
		{
			setCountEnable = false;
			break;
		}
	}
	
	QMenu menu;
	QAction *actStart = menu.addAction(tr("单点启动"));
	QAction *actStop = menu.addAction(tr("单点停止"));
	menu.addSeparator();
	QAction *actResume = menu.addAction(tr("接续"));
	QAction *actJump = menu.addAction(tr("工步跳转"));
	QAction *actFinish = menu.addAction(tr("结束"));	
	menu.addSeparator();
	QAction *actPara = menu.addAction(tr("通道并联"));
	QAction *actInfo = menu.addAction(tr("通道信息"));
	QAction *actData = menu.addAction(tr("通道数据"));
	QMenu *sub = menu.addMenu(tr("导出数据"));
	QAction *actExport = sub->addAction(tr("导出excel"));
    menu.addSeparator();
    
    QAction *actNum = menu.addAction(tr("显示数量"));
	QAction *foucsNum = menu.addAction(tr("强制显示全部"));
    QMenu *per_menu = menu.addMenu(tr("显示比例"));    
    QAction *actPer75 = per_menu->addAction(tr("小"));
    QAction *actPer100 = per_menu->addAction(tr("正常"));
	actNum->setEnabled(setCountEnable);

	QVector<int> items;   //item 索引
	QVector<int> channels[MAX_SUB_DEVICE]; //每个子设备通道索引
	QVector<QAction*> stepCmd;
	for (int i = 0; i < m_itemList.size(); i++)
	{
		if (m_itemList[i]->isChecked())
		{
			uchar sub_id = i / 8;			
			channels[sub_id].push_back(i);
			items.push_back(i);
		}
	}

	//并联判断
	if (item->scale() > 1)	
		actPara->setText(tr("解除并联"));	
	else
	{
		bool para = true;
		int first_sub = first_item / 8;
		//判断在一行，且非并联
		for (int i = 0; i < items.size(); i++)
		{
			if (items[i] / 8 != first_sub || m_itemList[items[i]]->scale() != 1)				
			{
				para = false;
				break;
			}
		}
		//判断数量
		para &= items.size() >= 2;
		actPara->setEnabled(para);
	}
	for (int i = 0; i < items.size(); i++)
	{
		if(m_itemList[items[i]]->status() != DeviceInfo::Status_Complete
			&& m_itemList[items[i]]->status() != DeviceInfo::Status_Stop)
			actPara->setEnabled(false);

		/* 有错误时不能完成 */
		//if (m_itemList[items[i]]->data().PrtCode != 0)
        //	finishEnable = false;
	}
	if (!paraEnable)
		actPara->setEnabled(false);

	stepCmd << actStart << actStop << actResume << actJump << actPara;
	actStart->setEnabled(startEnable);
	actStop->setEnabled(stopEnable);
	actResume->setEnabled(resumeEnable);
	actFinish->setEnabled(netId > 0 && finishEnable);
	actJump->setEnabled(stopEnable);	

    actFinish->setVisible(false);
	foucsNum->setVisible(false);

	//菜单
	QAction *act = menu.exec(item->mapToGlobal(pt));	
	if (stepCmd.indexOf(act) >= 0)
	{
		if (!checkDevice(netId))  //可能已经离线
			return;

		auto &device = m_devices[netId];
		if (act == actStart)
		{		
			//启动采集
			StepSettingDialog dlg;
			dlg.DeviceString = device.DeviceString;
			dlg.itemIndex = first_item;
			dlg.stepInfo = device.data->stepList[first_item];
			if (dlg.exec() == QDialog::Accepted)
			{
				if (!checkDevice(netId))
					return;

				if (finishEnable)
				{
					QVector<int> finishList;
					for (int i = 0; i < items.size(); i++)
					{
						int item_idx = items[i];
						if (device.DeviceStatus[item_idx] == DeviceInfo::Status_Stop)
							finishList << item_idx;
					}

					//软件层面，结束采集		
					recordsFinish(netId, finishList);
				}

				//设置工步
				stru_BTS_CMD_SET_STEP76_Ex step = dlg.stepInfo.step;
				step.clearChannel();
				memcpy(step.Guid, m_guid, 16);

				//启动
				stru_BTS_CMD_START70 start;
				memcpy(start.Guid, m_guid, 16);
				start.StepID = dlg.stepInfo.startStepIndex;
				
				QVector<int> itemList;
				QVector<BTEStepInfo> infoList;
				auto stepList = createPackList(device, channels, { DeviceInfo::Status_Complete,DeviceInfo::Status_Stop }, step,
					[&](int item_idx) {
					device.data->stepList[item_idx] = dlg.stepInfo;				
					itemList << item_idx;
					infoList << dlg.stepInfo;
				});				

				//开始采集								
				int ret = m_project.startRecords(DeviceString, itemList, infoList);
				for(int i = 0; i < itemList.size(); i++)					
					setItemStatus(netId, itemList[i], DeviceInfo::Status_WaitStart);

				//添加采集包
				QVector<StepRequestPtr> startList;
				for (int i = 0; i < stepList.size(); i++)
				{
					auto start_pack = new stru_BTS_CMD_START70(start);
					start_pack->SubDeviceId = stepList[i]->SubDeviceId;
					memcpy(start_pack->ChannelID, stepList[i]->ChannelID, sizeof(start_pack->ChannelID));

					startList.push_back(StepRequestPtr(start_pack));
				}

				sendParallel(netId, items);
				device.stepPackList << stepList << startList;
			}
		}
		else if (act == actStop)
		{
			//通知中位机停止	
			stru_BTS_CMD_STOP pack;
			auto stopList = createPackList(device, channels, { DeviceInfo::Status_Run }, pack,
				[&](int item_idx) {
				setItemStatus(netId, item_idx, DeviceInfo::Status_WaitStop);
			});

			device.stepPackList << stopList;			
		}
		else if (act == actResume)
		{
			//通知中位机恢复
			stru_BTS_CMD_RESUME pack;
			auto resumeList = createPackList(device, channels, { DeviceInfo::Status_Stop }, pack,
				[&](int item_idx) {
				setItemStatus(netId, item_idx, DeviceInfo::Status_WaitStart);
			});

			device.stepPackList << resumeList;			
			QVector<int> resume_items;
			for (int i = 0; i < items.size(); i++)
			{
				int item_idx = items[i];
				if (device.DeviceStatus[item_idx] == DeviceInfo::Status_WaitStart)
					m_project.resumeRecord(device.DeviceString, item_idx);
			}
		}
		else if (act == actJump)
		{
			StepJumpDialog dlg(this);
			dlg.stepInfo = device.data->stepList[first_item];
			if (dlg.exec() == QDialog::Accepted)
			{
				if (!checkDevice(netId))
					return;

				int select = dlg.select;
				//重置索引,只跳转工步相同的
				for (int i = 0; i < MAX_SUB_DEVICE; i++)
					channels[i].clear();
				for (int i = 0; i < m_itemList.size(); i++)
				{
					if (m_itemList[i]->isChecked() && device.data->stepList[first_item].step
						== device.data->stepList[first_item].step)
					{
						uchar sub_id = i / 8;
						channels[sub_id].push_back(i);
						items.push_back(i);
					}
				}

				stru_BTS_CMD_STEP_JUMP pack;
				pack.jumpStepId = select + 1;

				auto jumpList = createPackList(device, channels, { DeviceInfo::Status_Run }, pack,
					[&](int item_idx) {
					setItemStatus(netId, item_idx, DeviceInfo::Status_WaitJump);
				});

				device.stepPackList << jumpList;
			}
		}
		else if (act == actPara)
		{
			QGridLayout *layout = (QGridLayout *)ui.itemWidgets->layout();
			if (item->scale() == 1) //并联
			{
				//保存并联信息
				ParallelInfo info;
				info.channels = items;
				device.data->parallelList << info;			
			}
			else
			{
				for (int i = 0; i < device.data->parallelList.size(); i++)
				{
					if (device.data->parallelList[i].channels.contains(first_item))
					{
						auto pack = new stru_BTS_CMD_SEND_PARALLEL_CANCEL();
						auto p = device.data->parallelList[i];
						pack->SubDeviceId = p.channels[0] / 8;
						for (int i = 0; i < p.channels.size(); i++)
							pack->channelInfo.push_back(p.channels[i] % 8);

						send(netId, StepRequestPtr(pack));
						device.data->parallelList.remove(i);
						break;
					}
				}
			}
			updateItemLayout();
		}

		//发送数据包
		sendStepPack(netId);
	}
	else if (act == actFinish)
	{
		if (!checkDevice(netId))
			return;

		auto &device = m_devices[netId];
		QVector<int> finishList;
		for (int i = 0; i < items.size(); i++)
		{						
			int item_idx = items[i];
			if (device.DeviceStatus[item_idx] == DeviceInfo::Status_Stop)
				finishList << item_idx;
		}

		//软件层面，结束采集		
		recordsFinish(netId, finishList);
	}		
	else if (act == actInfo)
	{
		StepInfoDialog dlg(this);
		dlg.record = m_project.currentRecord(DeviceString, first_item);
		dlg.exec();
	}
	else if (act == actData || act == actExport)
	{
		auto record = m_project.currentRecord(DeviceString, first_item);
		m_project.loadRecordData(DeviceString,record);
		if (act == actData)
		{
			m_stepDataDialog->init(true, DeviceString,first_item,record);
			m_stepDataDialog->show();
		}
		else
		{			
			recordExport(DeviceString,record);
		}
	}
    else if (act == actNum)
    {
        auto device = m_project.deviceData(DeviceString);        

        bool ok;
        int enable_count = QInputDialog::getInt(this, tr("设置显示数量"),tr("显示数量"), device->enableItem, 1, 32, 1, &ok);
        if (!ok)
            return;

        device->enableItem = enable_count;
        for (int i = 0; i < m_itemList.size(); i++)
        {
			if (i >= enable_count)
				m_itemList[i]->setCheck(false);
            m_itemList[i]->setEnabled(i < enable_count);
			m_itemList[i]->setVisible(i < enable_count);            			
        }
		updateItemLayout();
    }
	else if (act == foucsNum) 
	{
		for (int i = 0; i < m_itemList.size(); i++)		
			m_itemList[i]->setVisible(true);
	}
    else if (act == actPer100 || act == actPer75)
    {
        auto device = m_project.deviceData(DeviceString);
        int size = (act == actPer100) ? Item_Normal : Item_Small;
        device->itemSize = size;
        for (int i = 0; i < m_itemList.size(); i++)
            m_itemList[i]->setSize(size);
    }    
}

void BTEServer::recordsStop(int netId, QVector<int> itemList)
{
	Q_ASSERT(m_devices.contains(netId));

	auto &deviceInfo = m_devices[netId];
	QString &DeviceString = deviceInfo.DeviceString;
	m_project.pauseRecords(DeviceString, itemList);
	for (int i = 0; i < itemList.size(); i++)
		setItemStatus(netId, itemList[i], DeviceInfo::Status_Stop);		
}

void BTEServer::recordsFinish(int netId, QVector<int> itemList)
{
	Q_ASSERT(m_devices.contains(netId));

	auto &deviceInfo = m_devices[netId];
	QString &DeviceString = deviceInfo.DeviceString;
	m_project.stopRecords(DeviceString, itemList);
	for (int i = 0; i < itemList.size(); i++)
		setItemStatus(netId, itemList[i], DeviceInfo::Status_Complete);
}

void BTEServer::recordExport(QString DeviceId,BTERecordPtr record)
{
	if (record->info.recordNum == 0)
	{
		QMessageBox::information(this, "", tr("无有效数据"));
		return;
	}

	auto timeStr = [](QDateTime time)->QString { return time.toString("yyyy-MM-dd hh:mm:ss"); };
	QString basename = "Record_" + record->info.recordTime.toString("yyyyMMdd_hhmmss") + ".xlsx";
	QString filename = QFileDialog::getSaveFileName(this, "", basename, "*.xlsx");
	if (filename.isEmpty())
		return;

	QString item_no = QString::asprintf("%02d-%02d", record->info.itemIndex / 8 + 1, record->info.itemIndex % 8 + 1);

	lxw_workbook  *workbook = workbook_new(filename.toLocal8Bit().data());

	lxw_format *format_normal = workbook_add_format(workbook);
	lxw_format *format_blue = workbook_add_format(workbook);
	lxw_format *iformat_blue = workbook_add_format(workbook);
	format_set_num_format(format_normal, "0.0000");

	format_set_bg_color(format_blue, qRgb(204, 255, 255));
	format_set_bg_color(format_blue, qRgb(204, 255, 255));
	format_set_num_format(format_blue, "0.0000");	

	auto fmt = [&format_blue, &format_normal](int idx) {return idx % 2 == 0 ? format_blue : format_normal; };
	auto ifmt = [&iformat_blue](int idx) {return idx % 2 == 0 ? iformat_blue : NULL; };

	/*info*/
	lxw_worksheet *sheet_info = workbook_add_worksheet(workbook, "Info");
	int row = 0;
	worksheet_write_string(sheet_info, row++, 0, qUtf8Printable(tr("测试信息")), NULL);
	worksheet_write_string(sheet_info, row++, 0, qUtf8Printable(timeStr(record->info.recordTime)), NULL);

	QStringList infoHeader;
	infoHeader << tr("设备") << tr("单元") << tr("通道") << tr("电池批号") << tr("电池编号") << tr("工步文件") << tr("开始时间")
		<< tr("结束时间") << tr("分选文件") << tr("等级") << tr("备注");
	for (int i = 0; i < infoHeader.size(); i++)
		worksheet_write_string(sheet_info, row, i, qUtf8Printable(infoHeader[i]), format_blue);
	row++;

	//设备
	worksheet_write_string(sheet_info, row, 0, qUtf8Printable(DeviceId), NULL);
	worksheet_write_number(sheet_info, row, 1, record->info.itemIndex / 8 + 1, NULL);
	worksheet_write_number(sheet_info, row, 2, record->info.itemIndex % 8 + 1, NULL);

	worksheet_write_string(sheet_info, row, 4, qUtf8Printable(record->info.stepInfo.cellBatch), NULL);
	worksheet_write_string(sheet_info, row, 5, qUtf8Printable(record->info.stepInfo.cellNo), NULL);
	worksheet_write_string(sheet_info, row, 6, qUtf8Printable(timeStr(record->info.recordTime)), NULL);
	worksheet_write_string(sheet_info, row, 7, qUtf8Printable(timeStr(record->info.recordTimeEnd)), NULL);

	worksheet_write_string(sheet_info, row, 10, qUtf8Printable(record->info.stepInfo.memo), NULL);


	/* statis */
	/*lxw_worksheet *sheet_statis = workbook_add_worksheet(workbook, "Statis");
	QStringList infoStatis;
	infoStatis << "通道" << "循环" << "步次" << "状态" << "起始电压(V)" << "结束电压(V)" << "起始电流(mA)"
		<< "结束电流(mA)" << "容量(mAh)" << "持续时间(h:min:s.ms)" << "相对时间(h:min:s.ms)" << "绝对时间";

	row = 0;
	for (int i = 0; i < infoStatis.size(); i++)
		worksheet_write_string(sheet_statis, row, i, qUtf8Printable(infoStatis[i]), fmt(i));

	row++;
	//循环项
	int pre_stepId = record->dataList[0].StepID;
	int start_idx = 0;
	int end_idx = -1;

	for (int i = 0; i < record->dataList.size(); i++)
	{
		auto &data = record->dataList[0];
		if(pre_stepId != data.StepID)
			end_idx = i - 1;			
		if (i == record->dataList.size() - 1)
			end_idx = i;

		if(end_idx > 0)
		{			
			auto &stepS = record->dataList[start_idx];
			auto &stepE = record->dataList[end_idx];
			QString step_name = PackDefine::StepNameList[stepS.StepType];

			worksheet_write_string(sheet_statis, row, 0, qUtf8Printable(item_no), fmt(0));
			worksheet_write_number(sheet_statis, row, 1, stepS.CycleStatus, fmt(1));

			worksheet_write_number(sheet_statis, row, 2, stepS.StepID, fmt(2));
			worksheet_write_string(sheet_statis, row, 3, qUtf8Printable(step_name), fmt(3));
			worksheet_write_number(sheet_statis, row, 4, DataTrans::fromVoltageUnit(stepS.Voltage), fmt(4));
			worksheet_write_number(sheet_statis, row, 5, DataTrans::fromVoltageUnit(stepE.Voltage), fmt(5));
			worksheet_write_number(sheet_statis, row, 6, DataTrans::fromElectricUnit(stepS.Current), fmt(6));
			worksheet_write_number(sheet_statis, row, 7, DataTrans::fromElectricUnit(stepE.Current), fmt(7));

			int time_gap = stepE.ATime.toMSec() - stepS.ATime.toMSec();
			worksheet_write_string(sheet_statis, row, 8,  qUtf8Printable(DataTrans::fromTimeUnitSec(time_gap)), fmt(8));
			worksheet_write_string(sheet_statis, row, 9,  qUtf8Printable(DataTrans::fromTimeUnitSec(time_gap)), fmt(9));
			worksheet_write_string(sheet_statis, row, 10, qUtf8Printable(stepE.ATime.toString()), fmt(10));

			start_idx = end_idx + 1;
		}
	}	
	*/

	/* detail */
	QStringList infoDetail;	
	infoDetail << tr("记录序号") << tr("状态") << tr("跳转") << tr("循环") << tr("步次") << tr("电流(A)") << tr("电压(V)")
		<< tr("容量(Ah)") << tr("能量(Wh)") << tr("相对时间(h:min:s.ms)") << tr("绝对时间");

	lxw_worksheet *sheet_detail = workbook_add_worksheet(workbook, "Detail");	
	row = 0;
	for (int i = 0; i < infoDetail.size(); i++)
		worksheet_write_string(sheet_detail, row, i, qUtf8Printable(infoDetail[i]), fmt(i));
	row++;
	for (int i = 0; i < record->dataList.size(); i++)
	{
		auto &step = record->dataList[i];

		QString step_name = PackDefine::StepNameList[step.StepType];

		worksheet_write_number(sheet_detail, row, 0, i + 1, ifmt(0));
		worksheet_write_string(sheet_detail, row, 1, qUtf8Printable(step_name), fmt(1));

		worksheet_write_number(sheet_detail, row, 2, step.StepChangeCount, ifmt(2));
		worksheet_write_number(sheet_detail, row, 3, step.CycleStatus, ifmt(3));
		worksheet_write_number(sheet_detail, row, 4, step.StepID, ifmt(4));

		worksheet_write_number(sheet_detail, row, 5, DataTrans::fromElectricUnit(step.Current), fmt(5));
		worksheet_write_number(sheet_detail, row, 6, DataTrans::fromVoltageUnit(step.Voltage), fmt(6));

		if (PackDefine::StepChargeType(step.StepType) == STEP_CHARGE)
		{
			worksheet_write_number(sheet_detail, row, 7, DataTrans::fromCapUnit(step.ChargeCap), fmt(7));
			worksheet_write_number(sheet_detail, row, 8, DataTrans::fromEngUnit(step.ChargeEng), fmt(8));
		}
		else if (PackDefine::StepChargeType(step.StepType) == STEP_DISCHARGE)
		{
			worksheet_write_number(sheet_detail, row, 7, DataTrans::fromCapUnit(step.DisChargeCap), fmt(7));
			worksheet_write_number(sheet_detail, row, 8, DataTrans::fromEngUnit(step.DisChargeEng), fmt(8));
		}

		worksheet_write_string(sheet_detail, row, 9, qUtf8Printable(DataTrans::fromTimeUnit(step.TestTime)), fmt(9));
		worksheet_write_string(sheet_detail, row, 10, qUtf8Printable(step.ATime.toString()), fmt(10));

		row++;
	}

	workbook_close(workbook);
}

bool BTEServer::recordDeleteWarning()
{
	if (QMessageBox::question(this, "", tr("是否删除数据"), QMessageBox::Yes | QMessageBox::No)
		== QMessageBox::Yes)
		return true;

	return false;
}

void BTEServer::recordDelete(int beforeDay)
{
	QDateTime currentDate = QDateTime::currentDateTime();
	QStringList deviceList = m_project.deviceList();

	for(int i = 0; i < deviceList.size(); i++)
	{
		QString deviceId = deviceList[i];
		const QMap<int, BTERecordInfo> &list = m_project.recordList(deviceId);
		QVector<int> bte_idxs;

		auto it = list.begin();
		while (it != list.end())
		{
			if (it->recordTimeEnd.daysTo(currentDate) > beforeDay)
				bte_idxs.push_back(it.key());

			it++;
		}

		m_project.deleteRecords(deviceId, bte_idxs);		
	}
}

void BTEServer::onBTEItemChecked(bool check)
{
	if (!check)
		return;	

	BTEItemWidget *item = qobject_cast<BTEItemWidget*>(sender());
	int item_idx = m_itemList.indexOf(item);
	for (int i = 0; i < m_itemList.size(); i++)
	{
		if (i != item_idx)
			m_itemList[i]->setCheck(false);
	}
}

void BTEServer::onBTEItemSelectAll()
{	
	auto data = m_project.deviceData(curDeviceString());
	for (int i = 0; i < m_itemList.size(); i++)
	{
		if(i < data->enableItem)
			m_itemList[i]->setCheck(true);
		else
			m_itemList[i]->setCheck(false);
	}
}

void BTEServer::initDeviceList()
{
	QString pre_device = curDeviceString();
	
	ui.listWidget->blockSignals(true);
	ui.listWidget->clear();

	//在线
	auto it = m_devices.begin();
	while (it != m_devices.end())
	{
		if (!it->DeviceString.isEmpty())
		{
			//add Item		
			QListWidgetItem *item = new QListWidgetItem();
			item->setText(it->DeviceString + tr("(在线)"));
			item->setData(Qt::UserRole, it.key());
			item->setData(Qt::UserRole + 1, it->DeviceString);
			ui.listWidget->addItem(item);
		}
		it++;
	}

	//离线
	QStringList list = m_project.deviceList();
	for (int i = 0; i < list.size(); i++)
	{
		if(deviceToNet(list[i]) < 0)
		{
			//add Item		
			QListWidgetItem *item = new QListWidgetItem();
			item->setText(list[i] + tr("(离线)"));
			item->setData(Qt::UserRole, -1);
			item->setData(Qt::UserRole + 1, list[i]);
			ui.listWidget->addItem(item);
		}
	}

	//当前id
	if (!pre_device.isEmpty())
	{		
		int item_num = ui.listWidget->count();		
		for (int i = 0; i < item_num; i++)
		{
			QString device_id = ui.listWidget->item(i)->data(Qt::UserRole + 1).toString();
			if (device_id == pre_device)			
				ui.listWidget->setCurrentRow(i);											
		}
	}
	ui.listWidget->blockSignals(false);

	//查询页
	ui.boxDevice->clear();
	ui.boxDevice->addItems(m_project.deviceList());

	initDevice();
}

void BTEServer::initDevice()
{		
    clearSplit();
	for (int i = 0; i < MAX_DEVICE_ITEM; i++)
	{
		m_itemList[i]->setCheck(false);
		m_itemList[i]->hide();
	}

	//normal		
	int cur_netId = curNetId();	
	QString deviceString = curDeviceString();
	if (deviceString.isEmpty())
		return;
	
	auto *deviceData = m_project.deviceData(deviceString);
	//clear
	int channelNum = deviceData->deviceInfo.channelNum();
	int list_colum = ui.tableList->columnCount();
	for (int i = 0; i < channelNum; i++)
	{
		m_itemList[i]->clear();
		m_itemList[i]->setVisible(true);		        
		m_itemList[i]->setStatus(DeviceInfo::Status_OffLine);

		for (int j = 1; j < list_colum; j++)
			ui.tableList->item(i, j)->setText("");		
		
		//update status
		if(cur_netId > 0)
			setItemStatus(cur_netId,i,m_devices[cur_netId].DeviceStatus[i]);		

		BTERecordPtr ptr = m_project.currentRecord(deviceString, i);
		setItemData(i, ptr->info.rtData);
		ui.tableList->item(i, 1)->setText(ptr->info.stepInfo.cellNo);		
	}

    for (int i = 0; i < m_itemList.size(); i++)
    {
        m_itemList[i]->setSize(deviceData->itemSize);
        m_itemList[i]->setEnabled(i < deviceData->enableItem);
		m_itemList[i]->setVisible(i < deviceData->enableItem);
    }

	updateItemLayout();
	updateItemLastDisEng();	    
}

void BTEServer::setItemData(int item_idx, const stru_BTS_CMD_SEND_RTDATA76 &pack)
{		
	//
	m_itemList[item_idx]->setData(pack);

	//list		
	QString title;
	QColor title_color;	
	m_itemList[item_idx]->getTitle(title,title_color);

	int id = pack.StepID;
	int loopTime = pack.CycleStatus;
	QString stepTime = DataTrans::fromTimeUnitSec(pack.TestTime);
	double current = DataTrans::fromElectricUnit(pack.Current);
	double voltage = DataTrans::fromVoltageUnit(pack.Voltage);	
	double cap = DataTrans::fromCapUnit(pack.ChargeCap);
	double dis_cap = DataTrans::fromCapUnit(pack.DisChargeCap);
	double eng = DataTrans::fromEngUnit(pack.ChargeEng);
	double dis_eng = DataTrans::fromEngUnit(pack.DisChargeEng);
	int prec = 4;
	
	ui.tableList->item(item_idx, 2)->setText(QString::number(pack.StepID));
	ui.tableList->item(item_idx, 3)->setText(QString::number(loopTime));
	ui.tableList->item(item_idx, 4)->setText(title);

	ui.tableList->item(item_idx, 5)->setText(QString::number(voltage,'f', prec) + "V");
	ui.tableList->item(item_idx, 6)->setText(QString::number(current,'f', prec) + "A");

	ui.tableList->item(item_idx, 7)->setText(stepTime);
	ui.tableList->item(item_idx, 8)->setText(QString::number(cap, 'f', prec));
	ui.tableList->item(item_idx, 9)->setText(QString::number(dis_cap, 'f', prec));

	ui.tableList->item(item_idx, 10)->setText(QString::number(eng, 'f', prec));
	ui.tableList->item(item_idx, 11)->setText(QString::number(dis_eng, 'f', prec));
}

void BTEServer::setItemLastChargeCap(int item_idx, double ChargeCap)
{
    ui.tableList->item(item_idx, 12)->setText(QString::number(ChargeCap, 'f', 4) + "Ah");
}

void BTEServer::setItemLastDisChargeCap(int item_idx, double disChargeCap)
{	
	ui.tableList->item(item_idx, 13)->setText(QString::number(disChargeCap,'f',4) + "Ah");	
}

void BTEServer::setItemStatus(int netId, int item_idx,int status)
{	
	Q_ASSERT(m_devices.contains(netId));
	m_devices[netId].DeviceStatus[item_idx] = status;

	if (netId == curNetId())
	{
		QString deviceId = m_devices[netId].DeviceString;
		m_itemList[item_idx]->setStatus(status);
		if (status == DeviceInfo::Status_WaitStart)
		{
			m_itemList[item_idx]->setData(stru_BTS_CMD_SEND_RTDATA76());
		}
		else if (status == DeviceInfo::Status_Run)
		{
			auto record = m_project.currentRecord(deviceId,item_idx);
			ui.tableList->item(item_idx, 1)->setText(record->info.stepInfo.cellNo);
		}
		
		QString title;  
		QColor title_color;
		m_itemList[item_idx]->getTitle(title, title_color);
		ui.tableList->item(item_idx, 4)->setText(title);		
	}
}

void BTEServer::on_listWidget_currentRowChanged(int currentRow)
{	
	initDevice();
}

void BTEServer::on_tabWidget_currentChanged(int index)
{
	bool isNormal = (index == 0);
	if (isNormal)
	{
		ui.stackedWidget->setCurrentWidget(ui.pageNormal);		
	}
	else
	{
		ui.boxDevice->clear();
		ui.boxDevice->addItems(m_project.deviceList());

		ui.dateFrom->setDate(QDate::currentDate());
		ui.dateTo->setDate(QDate::currentDate());

		ui.stackedWidget->setCurrentWidget(ui.pageHistory);
	}

	ui.btnShowNormal->setVisible(isNormal);
	ui.btnShowList->setVisible(isNormal);
	ui.btnShowSplitSetting->setVisible(isNormal);	
	ui.btnSplitQuery->setVisible(!isNormal);

#if !HIDE_TEST_PAGE
	ui.btnShowTest->setVisible(isNormal);
#endif
}

void BTEServer::on_tabWidgetSplit_currentChanged(int current)
{
	
}

void BTEServer::on_btnShowNormal_clicked()
{
	ui.stackedWidget->setCurrentWidget(ui.pageNormal);	
}

void BTEServer::on_btnShowList_clicked()
{
	ui.stackedWidget->setCurrentWidget(ui.pageList);
}

void BTEServer::on_btnShowSplitSetting_clicked()
{	
	ui.stackedWidget->setCurrentWidget(ui.pageSplit);
	ui.tabWidgetSplit->setCurrentIndex(0);

	ui.splitSettingWidget->init();
}

void BTEServer::on_btnShowTest_clicked()
{
	ui.stackedWidget->setCurrentWidget(ui.pageTest);
}

void BTEServer::onStepDataChannelChanged(int index)
{		
	if (m_stepDataDialog->isReal())
	{		
		QString DeviceString = m_stepDataDialog->deviceId();
		if (index >= 0 && index < 32)
		{
			auto record = m_project.currentRecord(DeviceString, index);
			m_project.loadRecordData(DeviceString,record);
			m_stepDataDialog->init(true, DeviceString, index, record);
		}
	}
	else
	{
		if (index >= 0 && index < m_queryRecordList.size())
		{
			auto query = m_queryRecordList[index];
			auto record = m_project.historyRecord(query.deviceId, query.record_id);
			m_project.loadRecordData(query.deviceId,record);
			m_stepDataDialog->init(false, query.deviceId, index, record);
		}			
	}
}

void BTEServer::on_btnQuery_clicked()
{
	ui.tableQuery->clearContents();

	QString deviceId = ui.boxDevice->currentText();
	if (deviceId.isEmpty())
	{
		QMessageBox::information(this, "", tr("请先选择数据"));
		return;
	}
	

	//判断条件
	int query_unit = -1; 
	int query_chn = -1;
	bool unit_ok = false, chn_ok = false;

	int dayNear = ui.lineNearDay->value();
	QDateTime from = ui.dateFrom->dateTime();
	QDateTime to = ui.dateTo->dateTime();
	to = to.addDays(1);
	QDateTime cur = QDateTime::currentDateTime();

	auto toInt = [](QLineEdit *edit,bool &ok) -> int{
			QString text = edit->text().trimmed();
			if (text.isEmpty())
			{
				ok = true;				
				return 0;
			}

			int ret = text.toInt(&ok);
			ok &= ret > 0;
			return ret;
		};
	
	query_unit = toInt(ui.lineQueryUnit,unit_ok);
	query_chn = toInt(ui.lineQueryChannel,chn_ok);
	
	if (!unit_ok || !chn_ok)
	{
		QMessageBox::information(this, "", tr("查询条件不正确"));
		return;
	}
	query_unit -= 1;
	query_chn -= 1;
	
	int timeType = 0;
	if (ui.btnNone->isChecked())
		timeType = 0;
	else if (ui.btnNear->isChecked())
		timeType = 1;
	else
		timeType = 2;
	
	QString creator = ui.lineCreator->text();
	QString cellPatch = ui.lineCellPatch->text();
	QString cellNum = ui.lineCellNum->text();

	//开始查询
	m_queryRecordList.clear();
	ui.tableQuery->clearContents();
	ui.tableQuery->setRowCount(0);

	const QMap<int, BTERecordInfo> &list = m_project.recordList(deviceId);
	QVector<int> bte_idxs;

	auto it = list.begin();
	while(it != list.end())
	{
		int i = it.key();
		int item_idx = list[i].itemIndex;
		int unit = item_idx / 8;
		int chn = item_idx % 8;
		auto &info = list[i];

		//通道筛选
		bool filter = false;
		if (query_unit != -1 && unit != query_unit)
			goto query_end;
		if (query_chn != -1 && query_chn != chn)
			goto query_end;
				
		//时间筛选				
		if ((timeType == 1 && info.recordTime.daysTo(cur) > dayNear)
			|| (timeType == 2 && (info.recordTime < from || info.recordTime > to)))
			goto query_end;

		//其他信息筛选			
		if (!creator.isEmpty() && !info.stepInfo.creator.contains(creator))
			goto query_end;

		if (!cellPatch.isEmpty() && !info.stepInfo.cellBatch.contains(cellPatch))
			goto query_end;

		if (!cellNum.isEmpty() && !info.stepInfo.cellNo.contains(cellNum))
			goto query_end;

		bte_idxs.push_back(i);		

query_end:
		it++;
	}
				
	ui.tableQuery->setRowCount(bte_idxs.size());
	for (int i = 0; i < bte_idxs.size(); i++)
	{
		auto &info = list[bte_idxs[i]];
		int item_idx = info.itemIndex;
		m_queryRecordList.push_back({ deviceId,info.itemIndex,info.recordId});
			
		int row = i;
		QTableWidgetItem *itemNO = new QTableWidgetItem(QString::number(row + 1));			
		QTableWidgetItem *itemChan = new QTableWidgetItem();
		QTableWidgetItem *itemTimeStrat = new QTableWidgetItem();
		QTableWidgetItem *itemTimeEnd = new QTableWidgetItem();
		QTableWidgetItem *itemRecordNum = new QTableWidgetItem();
		QTableWidgetItem *itemCreator = new QTableWidgetItem();
		QTableWidgetItem *itemCellNo = new QTableWidgetItem();
		QTableWidgetItem *itemCellPatch = new QTableWidgetItem();
		QTableWidgetItem *itemMemo = new QTableWidgetItem();
			
		itemChan->setText(QString::asprintf("%d-%d", item_idx/8 + 1, item_idx%8 + 1));
		itemTimeStrat->setText(info.recordTime.toString("yyyy-MM-dd HH:mm:ss"));
		itemTimeEnd->setText(info.recordTimeEnd.toString("yyyy-MM-dd HH:mm:ss"));
		itemRecordNum->setText(QString::number(info.recordNum));
		itemCreator->setText(info.stepInfo.creator);
		itemCellNo->setText(info.stepInfo.cellNo);
		itemCellPatch->setText(info.stepInfo.cellBatch);
		itemMemo->setText(info.stepInfo.memo);

		QCheckBox *itemCheck = new QCheckBox(tr("选择"));
		itemCheck->setChecked(true);
			
		ui.tableQuery->setItem(row, 0, itemNO);
		ui.tableQuery->setItem(row, 1, itemChan);
		ui.tableQuery->setCellWidget(row, 2, itemCheck);
		ui.tableQuery->setItem(row, 3, itemTimeStrat);
		ui.tableQuery->setItem(row, 4, itemTimeEnd);
		ui.tableQuery->setItem(row, 5, itemRecordNum);

		ui.tableQuery->setItem(row, 6, itemCellNo);
		ui.tableQuery->setItem(row, 7, itemCellPatch);
		ui.tableQuery->setItem(row, 8, itemCreator);
		ui.tableQuery->setItem(row, 9, itemMemo);
	}	

	if (ui.tableQuery->rowCount() == 0)
	{
		QMessageBox::information(this, "", tr("未找到相关数据"));
		return;
	}
}

void BTEServer::onTableQueryMenu(const QPoint &pos)
{
	auto item = ui.tableQuery->itemAt(pos);
	if (!item)
		return;
	int row = item->row();

	QMenu menu;	
	QAction *actInfo = menu.addAction(tr("通道信息"));
	QAction *actData = menu.addAction(tr("通道数据"));

	QMenu *sub = menu.addMenu(tr("导出数据"));
	QAction *actExport = sub->addAction(tr("导出excel"));

	QAction *act = menu.exec(ui.tableQuery->mapToGlobal(pos));
	if (act)
	{
		auto query = m_queryRecordList[row];
		auto record = m_project.historyRecord(query.deviceId, query.record_id);		
		if (act == actInfo)
		{
			StepInfoDialog dlg(this);
			dlg.record = record;
			dlg.exec();
		}
		else if (act == actData)
		{						
			m_project.loadRecordData(query.deviceId, record);
			m_stepDataDialog->init(false,query.deviceId,row,record);
			m_stepDataDialog->show();
		}
		else if (act == actExport)
		{			
			m_project.loadRecordData(query.deviceId, record);
			recordExport(query.deviceId,record);
		}
	}
}

void BTEServer::on_btnDeleteSelect_clicked()
{
	if (m_queryRecordList.size() == 0)
		return;

	if(!recordDeleteWarning())
		return;	

	QVector<int> idxs;
	for (int i = 0; i < m_queryRecordList.size(); i++)
		idxs << m_queryRecordList[i].record_id;

	m_project.deleteRecords(m_queryRecordList[0].deviceId, idxs);
	m_queryRecordList.clear();
	ui.tableQuery->clearContents();
	ui.tableQuery->setRowCount(0);
}

void BTEServer::on_btnDeleteWeek_clicked()
{
	if (!recordDeleteWarning())
		return;

	recordDelete(7);
	ui.tableQuery->clearContents();
}

void BTEServer::on_btnDeleteMonth_clicked()
{
	if (!recordDeleteWarning())
		return;

	recordDelete(30);
	ui.tableQuery->clearContents();
}

void BTEServer::on_btnDeleteYear_clicked()
{
	if (!recordDeleteWarning())
		return;

	recordDelete(365);
	ui.tableQuery->clearContents();
}

void BTEServer::on_btnDeleteDevice_clicked()
{
	QString deviceId = ui.boxDevice->currentText();
	if (deviceId.isEmpty())
		return;

	int netID = deviceToNet(deviceId);
	if (netID >= 0)
	{
		QMessageBox::information(this, "", tr("设备在线，无法删除"));
		return;
	}

	if (!recordDeleteWarning())
		return;
		
	ui.tableQuery->clearContents();		
	m_project.deleteDevice(deviceId);
	initDeviceList();	
}

void BTEServer::on_btnSplitQuery_clicked()
{	
	//分选
	int count = ui.tableQuery->rowCount();	
	QVector<BTERecordPtr> recordList;
	for (int i = 0; i < count; i++)
	{
		QCheckBox *box = (QCheckBox*)(ui.tableQuery->cellWidget(i, 2));
		if (box->isChecked())
		{
			auto query = m_queryRecordList[i];
			auto ptr = m_project.historyRecord(query.deviceId, query.record_id);
			recordList.push_back(ptr);
		}
	}

	if (recordList.size() == 0)
		return;

	SplitDialog dlg(this);
	dlg.recordList = recordList;
	dlg.exec();
}

void BTEServer::on_btnSaveSplit_clicked()
{
}

void BTEServer::onSplitPrepareData()
{
	//split
	QString deviceId = curDeviceString();
	if (deviceId.isEmpty())
		return;

	auto deviceData = m_project.deviceData(deviceId);
	ui.splitSettingWidget->recordList.clear();
	for (int i = 0; i < MAX_DEVICE_ITEM; i++)
	{
		ui.splitSettingWidget->recordList.push_back(deviceData->recordList[i]);
	}
}

void BTEServer::onSplitFinish()
{		
	auto split_ret = ui.splitSettingWidget->result;	
	if (split_ret.error)
	{
		QString tips = QString::asprintf(qUtf8Printable(tr("分选失败 设备%02d-%02d 无有效放电数据")), split_ret.erroNum / 8 + 1, split_ret.erroNum % 8 + 1);
		QMessageBox::information(this, "", tips);
		return;
	}	

	ui.tabWidgetSplit->setCurrentIndex(1);
	ui.splitPie->setResult(split_ret);
	m_splitResult = split_ret;

	auto range = ui.splitSettingWidget->range();
	ui.tableSplitLevel->clearContents();	
	ui.tableSplitLevel->setRowCount(16);
	int row = 0;
	for (int i = 0; i < 16; i++)
	{
		if (i == 15 || range[i].vaild)
		{
			QString name = (i < 15) ? QString('A' + i) : tr("落选");

			QCheckBox *itemNo = new QCheckBox(name);
			QTableWidgetItem *itemNum = new QTableWidgetItem(QString::number(split_ret.indexs[i].size()));
			QCheckBox *itemLed = new QCheckBox(tr("点灯"));
			itemNo->setProperty("level", i);
			itemLed->setProperty("level", i);

			connect(itemNo, SIGNAL(clicked()), this, SLOT(onSplitItemNo()));
			connect(itemLed, SIGNAL(clicked()), this, SLOT(onSplitItemLed()));

			ui.tableSplitLevel->setCellWidget(row, 0, itemNo);
			ui.tableSplitLevel->setItem(row, 1, itemNum);
			ui.tableSplitLevel->setCellWidget(row, 2, itemLed);

			row++;
		}
	}
	ui.tableSplitLevel->setRowCount(row);
	((QCheckBox*)ui.tableSplitLevel->cellWidget(0, 0))->setChecked(true);

	onSplitItemNo();
}

void BTEServer::onSplitItemNo()
{
	int idx = sender()->property("level").toInt();
	QVector<int> items;
	int count = ui.tableSplitLevel->rowCount();
	for (int i = 0; i < count; i++)
	{
		QCheckBox *box = (QCheckBox*)(ui.tableSplitLevel->cellWidget(i, 0));
		int level = box->property("level").toInt();		
		if (box->isChecked())
			items << m_splitResult.indexs[level];
	}

	//update table
	ui.tableSplitList->clearContents();
	ui.tableSplitList->setRowCount(items.size());
	for (int i = 0; i < items.size(); i++)
	{
		int item_idx = items[i];
		auto &info = m_splitResult.recordList[item_idx];

		QTableWidgetItem *itemNo = new QTableWidgetItem(QString::number(i + 1));
		QTableWidgetItem *itemPos = new QTableWidgetItem(QString::number(item_idx /8 + 1) + "-" + QString::number(item_idx%8 + 1));
		QTableWidgetItem *itemCap = new QTableWidgetItem(QString::number(info.DisChargeCap));
		QTableWidgetItem *itemTime = new QTableWidgetItem(info.TimeText());
		QTableWidgetItem *itemVolt = new QTableWidgetItem(QString::number(info.MeanVoltage));
		QTableWidgetItem *itemLevel = new QTableWidgetItem(info.LevelText());
		QTableWidgetItem *itemCellNo = new QTableWidgetItem(info.CellNo);

		ui.tableSplitList->setItem(i, 0, itemNo);
		ui.tableSplitList->setItem(i, 1, itemPos);
		ui.tableSplitList->setItem(i, 2, itemCap);
		ui.tableSplitList->setItem(i, 3, itemTime);
		ui.tableSplitList->setItem(i, 4, itemVolt);
		ui.tableSplitList->setItem(i, 5, itemLevel);
		ui.tableSplitList->setItem(i, 6, itemCellNo);
	}
	onSplitItemLed();
}

void BTEServer::onSplitItemLed()
{
    stru_BTS_CMD_LED_ON pack_led;
    memset(&pack_led.led, 0, sizeof(pack_led.led));

	int idx = sender()->property("level").toInt();
	QVector<int> items;
	int count = ui.tableSplitLevel->rowCount();
	for (int i = 0; i < count; i++)
	{
		QCheckBox *boxNo = (QCheckBox*)(ui.tableSplitLevel->cellWidget(i, 0));
		QCheckBox *box = (QCheckBox*)(ui.tableSplitLevel->cellWidget(i, 2));
		int level = box->property("level").toInt();
		if (boxNo->isChecked() && box->isChecked())
			items << m_splitResult.indexs[level];        
	}
	ui.splitResult->setChannels(items);

    for (int i = 0; i < items.size(); i++)
    {   
        int item_idx = items[i];        
        int row = item_idx / 8;
        int col = item_idx % 8;
        pack_led.led[row] |= 1 << col;        
    }

    int netId = curNetId();
    if (netId > 0)
    {
        QByteArray data;
        pack_led.pack(data);
        send(netId, BTS_CMD_LED_ON, data);
    }
}

void BTEServer::on_btnCopySplitList_clicked()
{    
    int count = ui.tableSplitList->rowCount();
    QVector<QStringList> text;
    QStringList head;
    head << tr("序号") << tr("设备-单元-通道") << tr("放电容量(Ah)") << tr("放电时间（H:M:S:MS）")
        << tr("中值电压（V）") << tr("级别") << tr("电池批号");

    text << head;
    for (int i = 0; i < count; i++)
    {
        QStringList line;
        for (int j = 0; j < 7; j++)
        {
            line << ui.tableSplitList->item(i, j)->text();
        }
        text << line;
    }
    ExportSplitResult(this, text);
}

bool BTEServer::ckeckConnect()
{
	if (curNetId() == -1)
	{
		QMessageBox::information(this, "", tr("请先连接设备"));
		return false;
	}
	return true;
}

void BTEServer::on_btnResetServer_clicked()
{
	m_devices.clear();
	ui.listWidget->clear();	
}

void BTEServer::on_btnClearSend_clicked()
{
	ui.textSend->clear();
}

void BTEServer::on_btnClearRecv_clicked()
{
	ui.textRecv->clear();
}

void BTEServer::clearSplit()
{
    m_splitResult = SplitResult();
	ui.tabWidgetSplit->setCurrentIndex(0);
    ui.splitSettingWidget->init();
    ui.splitResult->setChannels(QVector<int>());
    ui.splitPie->clear();
    ui.tableSplitLevel->clearContents();
	ui.tableSplitLevel->setRowCount(0);
    ui.tableSplitList->clearContents();
	ui.tableSplitList->setRowCount(0);
}

void BTEServer::updateItemLayout()
{			
	int size[MAX_DEVICE_ITEM];
	for (int i = 0; i < MAX_DEVICE_ITEM; i++)
	{
		m_itemList[i]->show();
		size[i] = 1;
	}

	QString deviceString = curDeviceString();
	DeviceData *deviceData = m_project.deviceData(deviceString);
	for (auto p : deviceData->parallelList)
	{
		size[p.channels[0]] = p.channels.size();
		for (int i = 1; i < p.channels.size(); i++)		
			size[p.channels[i]] = 0;
	}

	QGridLayout *layout = (QGridLayout *)ui.itemWidgets->layout();
	for(int row = 0; row < MAX_SUB_DEVICE; row++)
	{
		int col = 0;
		for (int i = 0; i < MAX_CHANNEL_NUM; i++)
		{
			int item_idx = row * 8 + i;								
			if (size[item_idx] > 0 && m_itemList[item_idx]->isEnabled())
			{
				m_itemList[item_idx]->setScale(size[item_idx]);
				layout->addWidget(m_itemList[item_idx], row, col, 1, size[item_idx]);
				col += size[item_idx];
			}
			else
			{
				m_itemList[item_idx]->hide();
				m_itemList[item_idx]->setCheck(false);
			}
		}
	}
}

void BTEServer::updateItemLastDisEng()
{	
	QString deviceString = curDeviceString();	
	DeviceData *deviceData = m_project.deviceData(deviceString);

	int channelNum = deviceData->deviceInfo.channelNum();
	for (int item_idx = 0; item_idx < channelNum; item_idx++)
	{
		//最后一个放电工步
		auto ptr = m_project.currentRecord(deviceString, item_idx);				
        if (ptr->info.dataCache.endCap > 0)
            setItemLastChargeCap(item_idx, ptr->info.dataCache.endCap);
		if (ptr->info.dataCache.endDisCap > 0)
			setItemLastDisChargeCap(item_idx, ptr->info.dataCache.endDisCap);
	}
}

void BTEServer::onHeartTime()
{
	QDateTime cur = QDateTime::currentDateTime();

	//重发数据包
	auto it = m_devices.begin();
	while (it != m_devices.end())
	{
		if (it->stepSendWait && it->stepSendTime.secsTo(cur) >= 5)
		{
			it->stepSendWait = false;
			sendStepPack(it.key());
		}

		//重新刷新状态
		if (it->stepPackList.size() == 0)
		{
			for (int i = 0; i < it->DeviceStatus.size(); i++)
			{
				if (it->DeviceStatus[i] == DeviceInfo::Status_WaitStart
					|| it->DeviceStatus[i] == DeviceInfo::Status_WaitStop
					|| it->DeviceStatus[i] == DeviceInfo::Status_WaitJump)
					setItemStatus(it.key(), i, DeviceInfo::Status_Error);
			}
		}

		it++;
	}

	//断线
	it = m_devices.begin();
	while (it != m_devices.end())
	{				
		if (it->heartTime.secsTo(cur) >= 60)
		{
			disconnect(it.key());
			break;
		}		
		it++;
	}
}

void BTEServer::onAutoTime()
{
	m_project.autoSave();
}

void BTEServer::onStepTime()
{
	auto it = m_devices.begin();
	while (it != m_devices.end())
	{
		sendStepPack(it.key());
		it++;
	}
}

void BTEServer::showWaitResume(DeviceInfo &device)
{
    QString deviceString = device.DeviceString;
    int netId = device.netId;
    if (m_waitResume.contains(deviceString))
        return;

    m_waitResume.insert(deviceString);
    QTimer::singleShot(0, [this, deviceString]() {
        QString text = tr("中位机") + deviceString + tr("工步异常中断，是否接续");
        int ret = QMessageBox::question(this, "", text, QMessageBox::Yes | QMessageBox::No);
		int netId = deviceToNet(deviceString);
		if (netId == -1)	 //可能已经断线
		{
			m_waitResume.remove(deviceString);
			return;
		}

        stru_BTS_CMD_RESUME_CONFIRM *confirm = new stru_BTS_CMD_RESUME_CONFIRM();
        confirm->result = (ret == QMessageBox::Yes) ? 1 : 2;
        m_devices[netId].stepPackList << StepRequestPtr(confirm);
        sendStepPack(netId);

        m_waitResume.remove(deviceString);
    });
}